/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * MultiSync.c
 *
 * synching issues
 * This file handles the constant backstream of net info, checking that recvd info
 * is concurrent with the local world, and correcting as required. Magic happens here.
 *
 * All conflicts due to non-guaranteed messaging are detected/resolved here.
 *
 * Alex Lee, pumpkin Studios, bath.
 */

#include "lib/framework/frame.h"
#include "lib/framework/input.h"
#include "lib/framework/strres.h"

#include "stats.h"
#include "lib/gamelib/gtime.h"
#include "map.h"
#include "objects.h"
#include "display.h"								// for checking if droid in view.
#include "order.h"
#include "action.h"
#include "hci.h"									// for byte packing funcs.
#include "display3ddef.h"							// tile size constants.
#include "console.h"
#include "geometry.h"								// for gettilestructure
#include "mapgrid.h"								// for move droids directly.
#include "lib/netplay/netplay.h"
#include "multiplay.h"
#include "frontend.h"								// for titlemode
#include "multistat.h"
#include "power.h"									// for power checks
#include "multirecv.h"

// ////////////////////////////////////////////////////////////////////////////
// function definitions

static BOOL sendStructureCheck	(void);							//Structure
static void packageCheck		(DROID *pD);
static BOOL sendDroidCheck		(void);							//droids

static void highLevelDroidUpdate(DROID *psDroid,
								 UDWORD x,
								 UDWORD y,
								 UDWORD state,
								 UDWORD order,
								 BASE_OBJECT *psTarget,
								 float experience);


static void onscreenUpdate		(DROID *pDroid,UDWORD dam,		// the droid and its damage
								 UDWORD x, UDWORD y,			// the ideal position
								 float fx,float fy,				// the ideal fractional position
								 UWORD dir,					// direction it should facing
								 DROID_ORDER order);			// what it should be doing

static void offscreenUpdate		(DROID *pDroid,UDWORD dam,
								 UDWORD x, UDWORD y,
								 float fx,float fy,
								 UWORD dir,
								 DROID_ORDER order);

static BOOL sendPowerCheck(void);
static UDWORD averagePing(void);

// ////////////////////////////////////////////////////////////////////////////
// Defined numeric values
#define AV_PING_FREQUENCY	45000					// how often to update average pingtimes. in approx millisecs.
#define PING_FREQUENCY		12000					// how often to update pingtimes. in approx millisecs.
#define STRUCT_FREQUENCY	700						// how often (ms) to send a structure check.
#define DROID_FREQUENCY		1000					// how ofter (ms) to send droid checks
#define POWER_FREQUENCY		10000					// how often to send power levels
#define SCORE_FREQUENCY		25000					// how often to update global score.

#define SYNC_PANIC			40000					// maximum time before doing a dirty fix.

static UDWORD				PingSend[MAX_PLAYERS];	//stores the time the ping was called.

// ////////////////////////////////////////////////////////////////////////////
// test traffic level.
static BOOL okToSend(void)
{
	//update checks	& go no further if any exceeded.
	if (NETgetRecentBytesSent() + NETgetRecentBytesRecvd() >= MAX_BYTESPERSEC)
	{
		return false;
	}

	return true;
}

// ////////////////////////////////////////////////////////////////////////////
// Droid checking info. keep position and damage in sync.
BOOL sendCheck(void)
{
	UDWORD i;

	NETgetBytesSent();			// update stats.
	NETgetBytesRecvd();
	NETgetPacketsSent();
	NETgetPacketsRecvd();

	// dont send checks till all players are present.
	for(i=0;i<MAX_PLAYERS;i++)
	{
		if(isHumanPlayer(i) && ingame.JoiningInProgress[i])
		{
			return true;
		}
	}

	sendPing();

	// send Checks. note each send has it's own send criteria, so might not send anything.

	if(okToSend())
	{
		sendStructureCheck();
	}
	if(okToSend())
	{
		sendPowerCheck();
	}
	if(okToSend())
	{
		sendScoreCheck();
	}
	if(okToSend())
	{
		sendDroidCheck();
	}
	return true;
}

// ////////////////////////////////////////////////////////////////////////////
// pick a droid to send, NULL otherwise.
static DROID* pickADroid(void)
{
	DROID* pD = NULL;						// current droid we're checking
	unsigned int i;
	static UDWORD	droidnum=0;						// how far down the playerlist to go.
	static UDWORD	player=0;						// current player we're checking
	static UDWORD	maxtrys=0;

	// Don't send stuff that isn't our problem
	while (!myResponsibility(player))
	{
		player = (player + 1) % MAX_PLAYERS;
		droidnum = 0;

		// Bail out if we've tried for each available player already
		if (++maxtrys >= MAX_PLAYERS)
		{
			maxtrys = 0;
			return NULL;
		}
	}

	// Find the 'droidnum'th droid for the current player
	for (i = 0, pD = apsDroidLists[player];
		i < droidnum && pD != NULL;
		++i, pD = pD->psNext)
	{}

	// If we've already dealt with the last droid for this player
	if (pD == NULL) // droid is no longer there or list end.
	{
		// Deal with the next player now
		player = (player + 1) % MAX_PLAYERS;
		droidnum = 0;

		// Bail out if we've tried for each available player already
		if (++maxtrys >= MAX_PLAYERS)
		{
			maxtrys = 0;
			return NULL;
		}

		// Invoke ourselves to pick a droid from the next player
		return pickADroid();
	}

	++droidnum;
	maxtrys = 0;

	return pD;
}
// ///////////////////////////////////////////////////////////////////////////
// force a droid sync
// We need to update the droid in question ASAP, to prevent 'lost' droids until
// the next 'real' update is done.
BOOL ForceDroidSync( DROID* droidToSend )
{
	uint8_t count = 1;		// *always* one
	if(!droidToSend) return false;
	debug(LOG_SYNC,"Force sync of droid %d from player %d",droidToSend->id,droidToSend->player);
	NETbeginEncode(NET_CHECK_DROID, NET_ALL_PLAYERS);
		NETuint8_t(&count);
		packageCheck(droidToSend);
	return NETend();
}
// ///////////////////////////////////////////////////////////////////////////
// send a droid info packet.
static BOOL sendDroidCheck(void)
{
	DROID			*pD, **ppD;
	uint8_t			i, count;
	static UDWORD	lastSent = 0;		// Last time a struct was sent.
	UDWORD			toSend = 6;

	if (lastSent > gameTime)
	{
		lastSent= 0;
	}

	// Only send a struct send if not done recently.
	if (gameTime - lastSent < DROID_FREQUENCY)
	{
		return true;
	}

	debug(LOG_SYNC, "sent droid check at tick %u", (unsigned int)gameTime);

	lastSent = gameTime;

	NETbeginEncode(NET_CHECK_DROID, NET_ALL_PLAYERS);

		// Allocate space for the list of droids to send
		ppD = alloca(sizeof(DROID *) * toSend);

		// Get the list of droids to sent
		for (i = 0, count = 0; i < toSend; i++)
		{
			pD = pickADroid();

			// If the droid is valid add it to the list
			if (pD)
			{
				ppD[count++] = pD;
			}
		}

		// Send the number of droids to expect
		NETuint8_t(&count);

		// Add the droids to the packet
		for (i = 0; i < count; i++)
		{
			packageCheck(ppD[i]);
		}

	return NETend();
}

// ////////////////////////////////////////////////////////////////////////////
// Send a Single Droid Check message
static void packageCheck(DROID *pD)
{
	// Send the player to which the droid belongs
	NETuint8_t(&pD->player);

	// Now the droid's ID
	NETuint32_t(&pD->id);

	// The droid's order
	NETint32_t(&pD->order);

	// The droids secondary order
	NETuint32_t(&pD->secondaryOrder);

	// Droid's current HP
	NETuint32_t(&pD->body);

	// Direction it is going in
	NETfloat(&pD->direction);

	// Fractional move
	if (pD->order == DORDER_ATTACK
	 || pD->order == DORDER_MOVE
	 || pD->order == DORDER_RTB
	 || pD->order == DORDER_RTR)
	{
		NETfloat(&pD->sMove.fx);
		NETfloat(&pD->sMove.fy);
	}
	// Non-fractional move, send regular coords
	else
	{
		NETuint16_t(&pD->pos.x);
		NETuint16_t(&pD->pos.y);
	}


	if (pD->order == DORDER_ATTACK)
	{
		NETuint32_t(&pD->psTarget->id);
	}
	else if (pD->order == DORDER_MOVE)
	{
		NETuint16_t(&pD->orderX);
		NETuint16_t(&pD->orderY);
	}

	// Last send the droid's experience
	NETfloat(&pD->experience);
}


// ////////////////////////////////////////////////////////////////////////////
// receive a check and update the local world state accordingly
BOOL recvDroidCheck()
{
	uint8_t		count;
	int		i;

	debug(LOG_SYNC, "recvDroidCheck");

	NETbeginDecode(NET_CHECK_DROID);

		// Get the number of droids to expect
		NETuint8_t(&count);

		for (i = 0; i < count; i++)
		{
			DROID		*pD;
			BASE_OBJECT	*psTarget = NULL;
			float		fx = 0, fy = 0;
			DROID_ORDER	order = 0;
			BOOL		onscreen;
			uint8_t		player;
			float		direction, experience;
			uint16_t	x = 0, y = 0, tx, ty;
			uint32_t	ref, body, target = 0, secondaryOrder;

			// Fetch the player
			NETuint8_t(&player);

			// Fetch the droid being checked
			NETuint32_t(&ref);

			// The droid's order
			NETenum(&order);

			// Secondary order
			NETuint32_t(&secondaryOrder);

			// HP
			NETuint32_t(&body);

			// Direction
			NETfloat(&direction);

			// Fractional move
			if (order == DORDER_ATTACK
			 || order == DORDER_MOVE
			 || order == DORDER_RTB
			 || order == DORDER_RTR)
			{
				NETfloat(&fx);
				NETfloat(&fy);
			}
			// Regular move
			else
			{
				NETuint16_t(&x);
				NETuint16_t(&y);
			}

			// Find out what the droid is aiming at
			if (order == DORDER_ATTACK)
			{
				NETuint32_t(&target);
			}
			// Else if the droid is moving where to
			else if (order == DORDER_MOVE)
			{
				NETuint16_t(&tx);
				NETuint16_t(&ty);
			}

			// Get the droid's experience
			NETfloat(&experience);

			/*
			 * Post processing
			 */

			// Find the droid in question
			if (!IdToDroid(ref, player, &pD))
			{
				NETlogEntry("Recvd Unknown droid info. val=player",0,player);
				debug(LOG_SYNC, "Received checking info for an unknown (as yet) droid. player:%d ref:%d", player, ref);
				continue;
			}

			// If there is a target find it
			if (target)
			{
				psTarget = IdToPointer(target, ANYPLAYER);
			}

			/*
			 * Decide how best to sync the droid. If it is onscreen and visible
			 * and the player who owns the droid has a low ping then do an
			 * onscreen update, otherwise do an offscreen one.
			 */
			if (DrawnInLastFrame(pD->sDisplay.frameNumber)
			 && pD->sDisplay.screenX < pie_GetVideoBufferWidth()
			 && pD->sDisplay.screenY < pie_GetVideoBufferHeight()
			 && ingame.PingTimes[player] < PING_LIMIT)
			{
				onscreen = true;
			}
			else
			{
				onscreen = false;
			}

			// Update the droid
			if (onscreen || isVtolDroid(pD))
			{
				onscreenUpdate(pD, body, x, y, fx, fy, direction, order);
			}
			else
			{
				offscreenUpdate(pD, body, x, y, fx, fy, direction, order);
			}

			debug(LOG_SYNC, "difference in position for droid %u; was (%d, %d); did %s update",
			      (unsigned int)pD->id, (int)x - pD->pos.x, (int)y - pD->pos.y,
			      onscreen ? "onscreen" : "offscreen");

			// If our version is similar to the actual one make a note of it
			if (abs(x - pD->pos.x) < TILE_UNITS * 2
			 || abs(y - pD->pos.y) < TILE_UNITS * 2)
			{
				pD->lastSync = gameTime;
			}

			// Update the higher level stuff
			if (!isVtolDroid(pD))
			{
				highLevelDroidUpdate(pD, x, y, secondaryOrder, order, psTarget, experience);
			}

			// ...and repeat!
		}

	NETend();

	return true;
}

// ////////////////////////////////////////////////////////////////////////////

// ////////////////////////////////////////////////////////////////////////////
// higher order droid updating. Works mainly at the order level. comes after the main sync.
static void highLevelDroidUpdate(DROID *psDroid,UDWORD x, UDWORD y,
								 UDWORD state, UDWORD order,
								 BASE_OBJECT *psTarget,float experience)
{
	// update kill rating.
	psDroid->experience = experience;

	// remote droid is attacking, not here tho!
	if(order == DORDER_ATTACK && psDroid->order != DORDER_ATTACK && psTarget)
	{
		turnOffMultiMsg(true);
		orderDroidObj(psDroid, DORDER_ATTACK, psTarget);
		turnOffMultiMsg(false);
	}

	// secondary orders.
	if(psDroid->secondaryOrder != state)
	{
		psDroid->secondaryOrder = state;
	}

	// see how well the sync worked, optionally update.
	// offscreen updates will make this ok each time.
	if(psDroid->order == DORDER_NONE && order == DORDER_NONE)
	{
		if(  (abs(x- psDroid->pos.x)>(TILE_UNITS*2))		// if more than 2 tiles wrong.
		   ||(abs(y- psDroid->pos.y)>(TILE_UNITS*2)) )
		{
			turnOffMultiMsg(true);
			orderDroidLoc(psDroid, DORDER_MOVE,x,y);
			turnOffMultiMsg(false);
		}
	}
}

// ////////////////////////////////////////////////////////////////////////////
// droid on screen needs modifying
static void onscreenUpdate(DROID *psDroid,
						   UDWORD dam,
						   UDWORD x,
						   UDWORD y,
						   float fx,
						   float fy,
						   UWORD dir,
						   DROID_ORDER order)
{

	BASE_OBJECT *psClickedOn;
	BOOL		bMouseOver = false;

	psClickedOn = mouseTarget();
	if( psClickedOn != NULL && psClickedOn->type == OBJ_DROID)
	{
		if(psClickedOn->id == psDroid->id && mouseDown(MOUSE_RMB))
		{
			bMouseOver = true;						// override, so you dont see the updates.
		}
	}

	if(!bMouseOver)
	{
		psDroid->body = dam;						// update damage
	}

//	if(psDroid->order == DORDER_NONE || (psDroid->order == DORDER_GUARD && psDroid->action == DACTION_NONE) )
//	{
//		psDroid->direction	 = dir  %360;				//update rotation
//	}

	return;
}

// ////////////////////////////////////////////////////////////////////////////
// droid offscreen needs modyfying.
static void offscreenUpdate(DROID *psDroid,
							UDWORD dam,
							UDWORD x,
							UDWORD y,
							float fx,
							float fy,
							UWORD dir,
							DROID_ORDER order)
{
	UDWORD				oldx,oldy;
	PROPULSION_STATS	*psPropStats;
 	SDWORD			xdiff,ydiff, distSq;

	// stage one, update the droid's position & info, LOW LEVEL STUFF.
	if(	   order == DORDER_ATTACK
		|| order == DORDER_MOVE
		|| order ==	DORDER_RTB
		|| order == DORDER_RTR)	// move order
	{

		// calculate difference between remote and local
		xdiff = psDroid->pos.x - (UWORD)fx;
		ydiff = psDroid->pos.y - (UWORD)fy;
		distSq = (xdiff*xdiff) + (ydiff*ydiff);

		// if more than  2 squares, jump it.
		if(distSq > (2*TILE_UNITS)*(2*TILE_UNITS) )
		{
			if( ((UDWORD)fx != 0) && ((UDWORD)fy != 0) )
			{
				oldx = psDroid->pos.x;
				oldy = psDroid->pos.y;
				debug(LOG_SYNC, "Jumping droid %d from (%u,%u) to (%u,%u)", (int)psDroid->id, oldx, oldy, (UDWORD)fx, (UDWORD)fy);

				psDroid->sMove.fx = fx;							//update x
				psDroid->sMove.fy = fy;							//update y

				psDroid->pos.x		 = (UWORD) fx;					//update move progress
				psDroid->pos.y		 = (UWORD) fy;
				gridMoveDroid(psDroid, (SDWORD)oldx,(SDWORD)oldy);

				psDroid->direction = dir % 360;		// update rotation

				// reroute the droid.
				turnOffMultiMsg(true);
				moveDroidTo(psDroid, psDroid->sMove.DestinationX,psDroid->sMove.DestinationY);
				turnOffMultiMsg(false);
			}
		}
	}
	else
	{
		oldx = psDroid->pos.x;
		oldy = psDroid->pos.y;
		debug(LOG_SYNC, "Moving droid %d from (%u,%u) to (%u,%u)", (int)psDroid->id, oldx, oldy, (UDWORD)fx, (UDWORD)fy);
		psDroid->pos.x		 = (UWORD)x;						//update x
		psDroid->pos.y		 = (UWORD)y;						//update y
		gridMoveDroid(psDroid, (SDWORD)oldx,(SDWORD)oldy);
		psDroid->direction = dir % 360;				// update rotation
	}

	psDroid->body		= dam;								// update damage

	// stop droid if remote droid has stopped.
	if ((order == DORDER_NONE || order == DORDER_GUARD)
	    && !(psDroid->order == DORDER_NONE || psDroid->order == DORDER_GUARD))
	{
		turnOffMultiMsg(true);
		moveStopDroid(psDroid);
		turnOffMultiMsg(false);
	}

	// snap droid(if on ground)  to terrain level at x,y.
	psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
	ASSERT( psPropStats != NULL, "offscreenUpdate: invalid propulsion stats pointer" );
	if(	psPropStats->propulsionType != LIFT )		// if not airborne.
	{
		psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);
	}
	return;
}


// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Structure Checking, to ensure smoke and stuff is consistent across machines.
// this func is recursive!
static  STRUCTURE *pickAStructure(void)
{
	static UDWORD	player=0;					// player currently checking.
	static UDWORD	snum=0;						// structure index for this player.
	STRUCTURE		*pS=NULL;
	static UDWORD	maxtrys = 0;				// don't loop forever if failing/.
	UDWORD			i;

	if ( !myResponsibility(player) )			// dont send stuff that's not our problem.
	{
		player ++;								// next player next time.
		player = player%MAX_PLAYERS;
		snum =0;

		if(maxtrys<MAX_PLAYERS)
		{
			maxtrys ++;
			return pickAStructure();
		}
		else
		{
			maxtrys = 0;
			return NULL;
		}
	}

	pS = apsStructLists[player];				// find the strucutre
	for(i=0; ((i<snum) && (pS != NULL)) ;i++)
	{
		pS= pS->psNext;
	}


	if (pS == NULL)								// last structure or no structures at all
	{
		player ++;								// go onto the next player
		player = player%MAX_PLAYERS;
		snum=0;

		if(maxtrys<MAX_PLAYERS)
		{
			maxtrys ++;
			return pickAStructure();
		}
		else
		{
			maxtrys = 0;
			return NULL;
		}
	}
	snum ++;										// next structure next time

	maxtrys = 0;
	return pS;
}

// ////////////////////////////////////////////////////////////////////////
// Send structure information.
static BOOL sendStructureCheck(void)
{
	static UDWORD	lastSent = 0;	// Last time a struct was sent
	STRUCTURE		*pS;
    uint8_t			capacity;

	if (lastSent > gameTime)
	{
		lastSent = 0;
	}

	if ((gameTime - lastSent) < STRUCT_FREQUENCY)	// Only send a struct send if not done recently
	{
		return true;
	}

	lastSent = gameTime;


	pS = pickAStructure();
	// Only send info about complete buildings
	if (pS && (pS->status == SS_BUILT))
	{
		NETbeginEncode(NET_CHECK_STRUCT, NET_ALL_PLAYERS);
			NETuint8_t(&pS->player);
			NETuint32_t(&pS->id);
			NETuint32_t(&pS->body);
			NETuint32_t(&pS->pStructureType->ref);
			NETuint16_t(&pS->pos.x);
			NETuint16_t(&pS->pos.y);
			NETuint16_t(&pS->pos.z);
			NETfloat(&pS->direction);

			switch (pS->pStructureType->type)
			{

				case REF_RESEARCH:
					capacity = ((RESEARCH_FACILITY *) pS->pFunctionality)->capacity;
					NETuint8_t(&capacity);
					break;
				case REF_FACTORY:
				case REF_VTOL_FACTORY:
					capacity = ((FACTORY *) pS->pFunctionality)->capacity;
					NETuint8_t(&capacity);
					break;
				case REF_POWER_GEN:
					capacity = ((POWER_GEN *) pS->pFunctionality)->capacity;
					NETuint8_t(&capacity);
				default:
					break;
			}

		NETend();
	}

	return true;
}

// receive checking info about a structure and update local world state
BOOL recvStructureCheck()
{
	STRUCTURE		*pS;
	STRUCTURE_STATS	*psStats;
	BOOL			hasCapacity = true;
	int				i, j;
	float			direction;
	uint8_t			player, ourCapacity;
	uint32_t		body;
	uint16_t		x, y, z;
	uint32_t		ref, type;

	NETbeginDecode(NET_CHECK_STRUCT);
		NETuint8_t(&player);
		NETuint32_t(&ref);
		NETuint32_t(&body);
		NETuint32_t(&type);
		NETuint16_t(&x);
		NETuint16_t(&y);
		NETuint16_t(&z);
		NETfloat(&direction);

		if (player >= MAX_PLAYERS)
		{
			debug(LOG_ERROR, "Bad NET_CHECK_STRUCT received!");
			NETend();
			return false;
		}

		// If the structure exists our job is easy
		pS = IdToStruct(ref, player);
		if (pS)
		{
			pS->body = body;
			pS->direction = direction;
		}
		// Structure was not found - build it
		else
		{
			NETlogEntry("scheck:structure check failed, adding struct. val=type", 0, type - REF_STRUCTURE_START);

			for (i = 0; i < numStructureStats && asStructureStats[i].ref != type; i++);
			psStats = &asStructureStats[i];

			// Check for similar buildings, to avoid overlaps
			if (TileHasStructure(mapTile(map_coord(x), map_coord(y))))
			{
				NETlogEntry("scheck:Tile has structure val=player", 0, player);

				pS = getTileStructure(map_coord(x), map_coord(y));

				// If correct type && player then complete & modify
				if (pS
				 && pS->pStructureType->type == type
				 && pS->player == player)
				{
					pS->direction = direction;
					pS->id = ref;

					if (pS->status != SS_BUILT)
					{
						pS->status = SS_BUILT;
						buildingComplete(pS);
					}

					NETlogEntry("scheck: fixed?", 0, player);
				}
				// Wall becoming a cornerwall
				else if (pS->pStructureType->type == REF_WALL)
				{
					if (psStats->type == REF_WALLCORNER)
					{
						NETlogEntry("scheck: fixed wall->cornerwall", 0, 0);
						removeStruct(pS, true);

						powerCalc(false);
						pS = buildStructure((STRUCTURE_STATS * )psStats, x, y, player, true);
						powerCalc(true);

						if (pS)
						{
							pS->id = ref;
						}
						else
						{
							NETlogEntry("scheck: failed to upgrade wall!", 0, player);
							return false;
						}
					}
				}
				else
				{
					NETlogEntry("scheck:Tile did not have correct type or player val=player",0,player);
					return false;
			    }
			}
			// Nothing exists there so lets get building!
			else
			{
				NETlogEntry("scheck: didn't find structure at all, building it",0,0);

				powerCalc(false);
				pS = buildStructure((STRUCTURE_STATS *) psStats, x, y, player, true);
				powerCalc(true);
			}
		}

		if (pS)
		{
			// Check its finished
			if (pS->status != SS_BUILT)
			{
				pS->direction = direction;
				pS->id = ref;
				pS->status = SS_BUILT;
				buildingComplete(pS);
			}

			// If the structure has a capacity
			switch (pS->pStructureType->type)
			{
				case REF_RESEARCH:
					ourCapacity = ((RESEARCH_FACILITY *) pS->pFunctionality)->capacity;
					j = researchModuleStat;
					break;
				case REF_FACTORY:
				case REF_VTOL_FACTORY:
					ourCapacity = ((FACTORY *) pS->pFunctionality)->capacity;
					j = factoryModuleStat;
					break;
				case REF_POWER_GEN:
					ourCapacity = ((POWER_GEN *) pS->pFunctionality)->capacity;
					j = powerModuleStat;
					break;
				default:
					hasCapacity = false;
					break;
			}

			// So long as the struct has a capacity fetch it from the packet
			if (hasCapacity)
			{
				uint8_t actualCapacity = 0;

				NETuint8_t(&actualCapacity);

				// If our capacity is different upgrade ourself
				for (; ourCapacity < actualCapacity; ourCapacity++)
				{
					buildStructure(&asStructureStats[j], pS->pos.x, pS->pos.y, pS->player, false);

					// Check it is finished
					if (pS && pS->status != SS_BUILT)
					{
						pS->id = ref;
						pS->status = SS_BUILT;
						buildingComplete(pS);
					}
				}
			}
		}

	NETend();
	return true;
}


// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Power Checking. Send a power level check every now and again.
static BOOL sendPowerCheck()
{
	static UDWORD	lastsent = 0;
	uint8_t			player = selectedPlayer;
	uint32_t		power = getPower(player);

	if (lastsent > gameTime)
	{
		lastsent = 0;
	}

	// Only send if not done recently
	if (gameTime - lastsent < POWER_FREQUENCY)
	{
		return true;
	}

	lastsent = gameTime;

	NETbeginEncode(NET_CHECK_POWER, NET_ALL_PLAYERS);
		NETuint8_t(&player);
		NETuint32_t(&power);
	return NETend();
}

BOOL recvPowerCheck()
{
	uint8_t		player;
	uint32_t	power, power2;

	NETbeginDecode(NET_CHECK_POWER);
		NETuint8_t(&player);
		NETuint32_t(&power);
	NETend();

	if (player >= MAX_PLAYERS)
	{
		debug(LOG_ERROR, "Bad NET_CHECK_POWER packet: player is %d", (int)player);
		debug(LOG_ERROR, "Bad NET_CHECK_POWER packet: player is %d : %s", 
		      (int)player, isHumanPlayer(player) ? "Human" : "AI");
		return false;
	}

	power2 = getPower(player);
	if (power != power2)
	{
		debug(LOG_NET, "NET_CHECK_POWER: Adjusting power for player %d (%s) from %u to %u",
		      (int)player, isHumanPlayer(player) ? "Human" : "AI", power2, power);
		setPower(player, power);
	}
	return true;
}

// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Score
BOOL sendScoreCheck(void)
{
	static UDWORD	lastsent = 0;
	uint8_t			i;
	BOOL			isData = false;
	PLAYERSTATS		stats;

	if (lastsent > gameTime)
	{
		lastsent= 0;
	}

	if (gameTime - lastsent < SCORE_FREQUENCY)
	{
		return true;
	}

	lastsent = gameTime;

	// Update local score
	stats = getMultiStats(selectedPlayer, true);

	// Add recently scored points
	stats.recentKills += stats.killsToAdd;
	stats.totalKills  += stats.killsToAdd;
	stats.recentScore += stats.scoreToAdd;
	stats.totalScore  += stats.scoreToAdd;

	// Zero them now added
	stats.killsToAdd = stats.scoreToAdd = 0;

	// Store local version
	setMultiStats(player2dpid[selectedPlayer], stats, true);

	// Send score to the ether
	setMultiStats(player2dpid[selectedPlayer], stats, false);

	// Broadcast any changes in other players, but not in FRONTEND!!!
	if (titleMode != MULTIOPTION && titleMode != MULTILIMIT)
	{
		NETbeginEncode(NET_SCORESUBMIT, NET_ALL_PLAYERS);

		for (i = 0; i < MAX_PLAYERS; i++)
		{
			if (isHumanPlayer(i) && i != selectedPlayer)
			{
				stats = getMultiStats(i, true);

				if (stats.killsToAdd || stats.scoreToAdd  )
				{
					NETuint8_t(&i);

					NETuint32_t(&stats.killsToAdd);
					NETuint32_t(&stats.scoreToAdd);

					isData = true;
				}
			}
		}

		// If we added any data to the packet
		if (isData)
		{
			// Terminate the message with ANYPLAYER
			uint8_t player = ANYPLAYER;
			NETuint8_t(&player);

			// Send the message
			NETend();
		}
	}

	// Get global versions of scores
	for (i = 0; i < MAX_PLAYERS; i++)
	{
		if (isHumanPlayer(i))
		{
			setMultiStats(player2dpid[i], getMultiStats(i, false), true);
		}
	}

	return true;
}


BOOL recvScoreSubmission()
{
	uint8_t		player;
	uint32_t	kills, score;
	PLAYERSTATS	stats;

	NETbeginDecode(NET_SCORESUBMIT);

	for (NETuint8_t(&player); player != ANYPLAYER; NETuint8_t(&player))
	{
		if (player == selectedPlayer)
		{
			NETuint32_t(&kills);
			NETuint32_t(&score);

			stats = getMultiStats(player, true);
			stats.killsToAdd += kills;
			stats.scoreToAdd += score;

			break;
		}
	}

	return true;
}

// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Pings

static UDWORD averagePing(void)
{
	UDWORD i,count,total;

	count =0;
	total =0;

	for(i=0;i<MAX_PLAYERS;i++)
	{
		if(isHumanPlayer(i))
		{
			total += ingame.PingTimes[i];
			count ++;
		}
	}
	return total/count;
}

BOOL sendPing(void)
{
	BOOL			isNew = true;
	uint8_t			player = selectedPlayer;
	int				i;
	static UDWORD	lastPing = 0;	// Last time we sent a ping
	static UDWORD	lastav = 0;		// Last time we updated average

	// Only ping every so often
	if (lastPing > gameTime)
	{
		lastPing = 0;
	}

	if (gameTime - lastPing < PING_FREQUENCY)
	{
		return true;
	}

	lastPing = gameTime;

	// If host, also update the average ping stat for joiners
	if (NetPlay.bHost)
	{
		if (lastav > gameTime)
		{
			lastav = 0;
		}

		if (gameTime - lastav > AV_PING_FREQUENCY)
		{
			NETsetGameFlags(2, averagePing());
			lastav = gameTime;
		}
	}

	/*
	 * Before we send the ping, if any player failed to respond to the last one
	 * we should re-enumerate the players.
	 */

	for (i = 0; i < MAX_PLAYERS; i++)
	{
		if (isHumanPlayer(i)
		 && PingSend[i]
		 && ingame.PingTimes[i]
		 && i != selectedPlayer)
		{
			ingame.PingTimes[i] = PING_LIMIT;
		}
		else if (!isHumanPlayer(i)
		      && PingSend[i]
		      && ingame.PingTimes[i]
		      && i != selectedPlayer)
		{
			ingame.PingTimes[i] = 0;
		}
	}

	NETbeginEncode(NET_PING, NET_ALL_PLAYERS);
		NETuint8_t(&player);
		NETbool(&isNew);
	NETend();

	// Note when we sent the ping
	for (i = 0; i < MAX_PLAYERS; i++)
	{
		PingSend[i] = gameTime2;
	}

	return true;
}

// accept and process incoming ping messages.
BOOL recvPing()
{
	BOOL	isNew;
	uint8_t	sender, us = selectedPlayer;

	NETbeginDecode(NET_PING);
		NETuint8_t(&sender);
		NETbool(&isNew);
	NETend();

	if (sender >= MAX_PLAYERS)
	{
		debug(LOG_ERROR, "Bad NET_PING packet, sender is %d", (int)sender);
		return false;
	}

	// If this is a new ping, respond to it
	if (isNew)
	{
		NETbeginEncode(NET_PING, player2dpid[sender]);
			// We are responding to a new ping
			isNew = false;

			NETuint8_t(&us);
			NETbool(&isNew);
		NETend();
	}
	// They are responding to one of our pings
	else
	{
		// Work out how long it took them to respond
		ingame.PingTimes[sender] = (gameTime2 - PingSend[sender]) / 2;

		// Note that we have received it
		PingSend[sender] = 0;
	}

	return true;
}
